﻿#region License
// Copyright 2007-2008 Rory Plaire (codekaizen@gmail.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Threading;
using NPack.Interfaces;

namespace NPack
{
    /// <summary>
    /// Defines a vector buffer for storing vector data in a specific format. The 
    /// <see cref="ManagedVectorBuffer{TVector, TComponent}"/> stores vectors
    /// on the managed heap, and is fully verifiable.
    /// </summary>
    /// <typeparam name="TVector">The type of vector to store.</typeparam>
    /// <typeparam name="TComponent">The type of component in the vector.</typeparam>
    /// <remarks>
    /// <para>
    /// A vector buffer allows specific storage of vectors in memory. This is useful
    /// since modern hardware treats vector data specially, and a specific storage
    /// format can take advantage of these capabilities. For example, SSE and SSE2 instructions
    /// are found on almost all of the non-embedded, general-purpose CPUs on the market,
    /// and these instructions work best when memory is aligned to 16-byte boundaries. 
    /// Using a Structure of Arrays (SoA) data format is also preferable when performing
    /// matrix computations.
    /// </para>
    /// </remarks>
    public class ManagedVectorBuffer<TComponent, TVector> : SynchronizedStorage, IVectorBuffer<TComponent, TVector>
        where TVector : IBufferedVector<TComponent, TVector>, IEquatable<TVector>,
                        IComparable<TVector>, IComputable<Double, TVector>
        where TComponent : IEquatable<TComponent>, IComparable<TComponent>,
                           IComputable<TComponent>, IConvertible, IFormattable
    {
        //private static readonly Int32 LargeObjectSizeInBytes = 85000;
        //private readonly Boolean _isComponentValueType = typeof(TComponent).IsValueType;
        //private readonly Boolean _isVectorValueType = typeof(TVector).IsValueType;
        //private readonly Int32 _homogenousComponentCount;
        //private readonly Int32 _vectorDimension;
        //private TComponent[] _components;
        //private BitArray _extentFreeList = new BitArray(0);
        //private readonly TComponent _one;

        [Flags]
        private enum ManagedVectorBufferState
        {
            ChangingExtent = 8,
        }

        internal static readonly BitVector32.Section VectorSection =
            BitVector32.CreateSection(ManagedVectorPage<TComponent, TVector>.PageSizeBytes - 1);

        internal static readonly BitVector32.Section PageSection =
            BitVector32.CreateSection(ManagedVectorExtent<TComponent, TVector>.ExtentSizeInPages - 1, VectorSection);

        internal static readonly BitVector32.Section ExtentSection =
            BitVector32.CreateSection(Int16.MaxValue - 1, PageSection);

        private readonly IBufferedVectorFactory<TComponent, TVector> _vectorFactory;
        private readonly List<ManagedVectorExtent<TComponent, TVector>> _extents
            = new List<ManagedVectorExtent<TComponent, TVector>>();
        private readonly List<UInt16> _extentDimensionList = new List<UInt16>();
        private readonly BitArray _extentsLocked = new BitArray(0);
        private Int32 _maxSize;
        private Int32 _vectorCount;

        /// <summary>
        /// Creates a new <see cref="ManagedVectorBuffer{TVector, TComponent}" />.
        /// </summary>
        /// <param name="vectorFactory">
        /// A factory used to create vectors of type <typeparamref name="TVector"/>.
        /// </param>
        public ManagedVectorBuffer(IBufferedVectorFactory<TComponent, TVector> vectorFactory)
            : this(vectorFactory, Int32.MaxValue) { }

        /// <summary>
        /// Creates a new <see cref="ManagedVectorBuffer{TVector, TComponent}" />. 
        /// </summary>
        /// <param name="vectorFactory">
        /// A factory used to create vectors of type <typeparamref name="TVector"/>.
        /// </param>
        /// <param name="maximumCapacity">
        /// The maximum capacity of the vector buffer.
        /// </param>
        public ManagedVectorBuffer(IBufferedVectorFactory<TComponent, TVector> vectorFactory,
                                   Int32 maximumCapacity)
        {
            if (maximumCapacity <= 0)
            {
                throw new ArgumentOutOfRangeException("maximumCapacity",
                                                      maximumCapacity,
                                                      "Parameter must be positive.");
            }

            _maxSize = maximumCapacity;
            _vectorFactory = vectorFactory;

            // TODO: this will fail if TComponent is a reference type. 
            // Should it be constrained?
            //_one = default(TComponent).One;
        }

        #region IVectorBuffer<TVector,TComponent> Members
        /// <summary>
        /// Adds components to the buffer as a vector. Returns the resulting
        /// <typeparamref name="TVector"/> instance.
        /// </summary>
        /// <param name="v0">The first component which make up the vector.</param>
        /// <param name="v1">The second component which make up the vector.</param>
        /// <returns>
        /// The resulting vector after adding the given components.
        /// </returns>
        public TVector Add(TComponent v0, TComponent v1)
        {
            Int32 vectorIndex = addVector(v0, v1);

            return _vectorFactory.CreateBufferedVector(this, vectorIndex);
        }

        /// <summary>
        /// Adds components to the buffer as a vector. Returns the resulting
        /// <typeparamref name="TVector"/> instance.
        /// </summary>
        /// <param name="v0">The first component which make up the vector.</param>
        /// <param name="v1">The second component which make up the vector.</param>
        /// <param name="v2">The second component which make up the vector.</param>
        /// <returns>
        /// The resulting vector after adding the given components.
        /// </returns>
        public TVector Add(TComponent v0, TComponent v1, TComponent v2)
        {
            Int32 vectorIndex = addVector(v0, v1, v2);

            return _vectorFactory.CreateBufferedVector(this, vectorIndex);
        }

        /// <summary>
        /// Adds an array of components to the buffer as a vector. Returns the resulting
        /// <typeparamref name="TVector"/> instance.
        /// </summary>
        /// <param name="components">The components which make up the vector.</param>
        /// <returns>
        /// The resulting vector after adding the given <paramref name="components"/>.
        /// </returns>
        public TVector Add(params TComponent[] components)
        {
            Int32 vectorIndex = addVector(components);

            return _vectorFactory.CreateBufferedVector(this, vectorIndex);
        }

        /// <summary>
        /// Adds an <see cref="IVector{TComponent}"/> to the buffer.
        /// </summary>
        /// <param name="item">The vector to add to the buffer.</param>
        /// <returns>The index of the added vector.</returns>
        public Int32 Add(IVector<TComponent> item)
        {
            return addVector(item.Components);
        }

        public Int32 Add(TVector vector)
        {
            return addVector(vector.Components);
        }

        /// <summary>
        /// Clears the buffer of all vector instances.
        /// </summary>
        public void Clear()
        {
            _extents.Clear();
        }

        /// <summary>
        /// Copies the buffer to the given array.
        /// </summary>
        /// <param name="array">The array to copy the buffer to.</param>
        /// <param name="startIndex">The index of the buffer to start copying at.</param>
        /// <param name="endIndex">The index of the buffer to end copying at.</param>
        public void CopyTo(TVector[] array, Int32 startIndex, Int32 endIndex)
        {
            if (array == null)
            {
                throw new ArgumentNullException("array");
            }

            if (startIndex < 0 || startIndex >= Count)
            {
                throw new ArgumentOutOfRangeException("startIndex", startIndex,
                                                      "Copy start index must be 0 or " +
                                                      "greater and less than " +
                                                      "the buffer's Count value.");
            }

            if (endIndex >= Count)
            {
                throw new ArgumentOutOfRangeException("endIndex", endIndex,
                                                      "Copy end index must be less than " +
                                                      "the buffer's Count value.");
            }

            Int32 length = (endIndex + 1) - startIndex;
            length = Math.Min(length, array.Length);

            for (Int32 destinationIndex = 0; destinationIndex < length; destinationIndex++)
            {
                TVector vector = _vectorFactory.CreateBufferedVector(
                    this, destinationIndex + startIndex);

                array[destinationIndex] = vector;
            }
        }

        /// <summary>
        /// Returns <see langword="true"/> if the buffer contains 
        /// the given <see cref="IVector{TComponent}"/>.
        /// </summary>
        /// <param name="item">The vector to check if the buffer contains.</param>
        /// <returns>
        /// <see langword="true"/> if the buffer contains <paramref name="item"/>;
        /// <see langword="false"/> otherwise.
        /// </returns>
        public Boolean Contains(IVector<TComponent> item)
        {
            // TODO: establish a behavior model for buffered vectors
            // What happens if a vector has an index which is on the removed list?
            //if (item is TVector)
            //{
            //    TVector vector = (TVector)item;

            //    if (ReferenceEquals(vector.GetBuffer(), this))
            //    {
            //        return true;
            //    }
            //}

            throw new NotImplementedException("Vector lookup by coordinates not implemented.");
        }

        public Boolean Contains(TVector item)
        {
            throw new NotImplementedException("Vector lookup not implemented.");
        }

        /// <summary>
        /// Gets the number of vectors in the buffer.
        /// </summary>
        public Int32 Count
        {
            get
            {
                return Thread.VolatileRead(ref _vectorCount);
            }
        }

        public Int32 GetVectorLength(Int32 index)
        {
            ManagedVectorExtent<TComponent, TVector> extent = getExtent(index);
            return extent.VectorLength;
        }

        /// <summary>
        /// Gets <see langword="true"/> if the buffer is read-only.
        /// </summary>
        public Boolean IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Gets or sets the maximum size of the buffer, in number of vectors.
        /// </summary>
        public Int32 MaximumSize
        {
            get { return _maxSize; }
            set
            {
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException("value", value,
                                                          "Maximum size must be greater " +
                                                          "than 0");
                }

                throw new NotImplementedException("Need to drop extents here...");

                _maxSize = value;
            }
        }

        public void Remove(Int32 index)
        {
            removeVector(index);
        }

        /// <summary>
        /// Gets or sets the <typeparamref name="TVector"/> at the given
        /// <paramref name="index"/>.
        /// </summary>
        /// <param name="index">The index of the vector to access.</param>
        /// <returns>
        /// The <typeparamref name="TVector"/> at <paramref name="index"/>.
        /// </returns>
        public TVector this[Int32 index]
        {
            get
            {
                //checkId(index);
                return _vectorFactory.CreateBufferedVector(this, index);
            }
            set
            {
                ManagedVectorExtent<TComponent, TVector> extent = getExtent(index);
                extent.SetVector(value, index);
            }
        }

        public TComponent this[Int32 index, Int32 component]
        {
            get
            {
                // get extent id from index, look it up and get the component
                ManagedVectorExtent<TComponent, TVector> extent = getExtent(index);
                return extent.GetVectorComponent(index, component);
            }
        }

        public event CancelEventHandler SizeIncreasing;

        public event EventHandler SizeIncreased;

        public event EventHandler<VectorOperationEventArgs<TComponent, TVector>> VectorChanged;

        ///// <summary>
        ///// Gets the length, or number of components, in the vectors stored
        ///// in the buffer.
        ///// </summary>
        //public Int32 VectorLength
        //{
        //    get { return _vectorDimension + _homogenousComponentCount; }
        //}

        //public IVectorFactory<TComponent, TVector> Factory
        //{
        //    get { throw new NotImplementedException(); }
        //}

        #endregion

        #region IEnumerable<IVector<TComponent>> Members

        public IEnumerator<TVector> GetEnumerator()
        {
            foreach (ManagedVectorExtent<TComponent, TVector> extent in _extents)
            {
                foreach (ManagedVectorPage<TComponent, TVector> page in extent)
                {
                    foreach (Int32 vectorId in page)
                    {
                        BitVector32 id = new BitVector32(0);
                        id[ExtentSection] = extent.ExtentId;
                        id[PageSection] = page.PageId;
                        id[VectorSection] = vectorId;

                        yield return _vectorFactory.CreateBufferedVector(this, id.Data);
                    }
                }
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        protected void OnSizeIncreasing(ref Boolean cancel)
        {
            CancelEventHandler e = SizeIncreasing;

            if (e != null)
            {
                CancelEventArgs args = new CancelEventArgs();
                e(this, args);
                cancel = args.Cancel;
            }
        }

        protected void OnSizeIncreased()
        {
            EventHandler e = SizeIncreased;

            if (e != null)
            {
                e(this, EventArgs.Empty);
            }
        }

        #region Private helper methods

        //private void checkVectorDimension(Int32 vectorDimension)
        //{
        //    if (vectorDimension <= 0)
        //    {
        //        throw new ArgumentOutOfRangeException("vectorDimension",
        //                                              vectorDimension,
        //                                              "Parameter must be positive.");
        //    }
        //}

        private void checkIndex(Int32 index)
        {
            if (index < 0 || index >= Count)
            {
                throw new ArgumentOutOfRangeException("index",
                                                      index,
                                                      "Index must be between 0 " +
                                                      "and Count.");
            }
        }

        private Int32 addVector(TComponent v0, TComponent v1)
        {
            return addVectorByDiscreteComponents(2, v0, v1, default(TComponent));
        }

        private Int32 addVector(TComponent v0, TComponent v1, TComponent v2)
        {
            return addVectorByDiscreteComponents(3, v0, v1, v2);
        }

        private int addVectorByDiscreteComponents(Int32 componentCount, TComponent v0, TComponent v1, TComponent v2)
        {
            ManagedVectorExtent<TComponent, TVector> extent = getFreeExtent(componentCount);
            Int32 vectorId = componentCount == 2 ? extent.AddVector(v0, v1) : extent.AddVector(v0, v1, v2);
            returnExtent(extent);
            Interlocked.Increment(ref _vectorCount);

            return vectorId;
        }

        private Int32 addVector(TComponent[] components)
        {
            Int32 componentCount = components.Length;
            ManagedVectorExtent<TComponent, TVector> extent = getFreeExtent(componentCount);
            Int32 vectorId = extent.AddVector(components);
            returnExtent(extent);
            Interlocked.Increment(ref _vectorCount);

            return vectorId;
        }

        private void removeVector(int index)
        {
            // Find extent and remove from it
            //Interlocked.Decrement(ref _vectorCount);
        }

        private ManagedVectorExtent<TComponent, TVector> getExtent(Int32 vectorId)
        {
            UInt16 extentId = (UInt16)(vectorId >> 16);
            checkIndex(extentId);
            return _extents[extentId];
        }

        private ManagedVectorExtent<TComponent, TVector> getFreeExtent(Int32 componentCount)
        {
            Int32 freeExtent = -1;
            ManagedVectorExtent<TComponent, TVector> extent;

            try
            {
                WaitOnSetState((Int32)ManagedVectorBufferState.ChangingExtent);

                do
                {
                    freeExtent = _extentDimensionList.FindIndex(freeExtent + 1,
                                                                delegate(UInt16 match)
                                                                {
                                                                    return match == componentCount;
                                                                });
                } while (freeExtent >= 0 && (_extents[freeExtent].IsFull || _extentsLocked[freeExtent]));

                if (freeExtent >= 0)
                {
                    extent = _extents[freeExtent];
                    _extentsLocked[freeExtent] = true;
                    // Use free area in buffer to store vector
                    //Int32 componentsIndex = vectorIndex * 2;
                    //_components[componentsIndex] = v0;
                    //_components[componentsIndex + 1] = v1;
                }
                else
                {
                    UInt16 extentId = (UInt16)_extents.Count;
                    extent = new ManagedVectorExtent<TComponent, TVector>(extentId, componentCount);
                    _extents.Add(extent);
                    _extentDimensionList.Add((UInt16)componentCount);
                    _extentsLocked.Length++;
                }

            }
            finally
            {
                UnsetState((Int32)ManagedVectorBufferState.ChangingExtent);
            }

            return extent;
        }

        private void returnExtent(ManagedVectorExtent<TComponent, TVector> extent)
        {
            _extentsLocked[extent.ExtentId] = false;
        }

        //private void expandIfNeeded()
        //{
        //    if (_nextVectorIndex >= _components.Length / VectorLength)
        //    {
        //        expandStorage();
        //    }
        //}

        //private void expandStorage()
        //{
        //    Boolean cancel = false;

        //    OnSizeIncreasing(ref cancel);

        //    if (cancel)
        //    {
        //        return;
        //    }

        //    Int64 expandedLength = _components.Length * 2;

        //    if (expandedLength > (Int64)MaximumSize * VectorLength)
        //    {
        //        expandedLength = MaximumSize * VectorLength;
        //    }

        //    if (expandedLength == _components.Length)
        //    {
        //        throw new InvalidOperationException(
        //            "Cannot expand buffer past MaximumSize.");
        //    }

        //    TComponent[] copy = new TComponent[expandedLength];
        //    Array.Copy(_components, copy, _components.Length);
        //    _components = copy;

        //    OnSizeIncreased();
        //}

        //private void setComponents(TComponent[] components, Int32 vectorId)
        //{
        //    Int32 vectorLength = VectorLength;
        //    Int32 dstOffset = vectorIndex * vectorLength;
        //    Int32 componentCount = components.Length;

        //    Array.Copy(components, 0, _components, dstOffset, componentCount);

        //    if (componentCount == vectorLength - 1)
        //    {
        //        Int32 homogenousComponentIndex = dstOffset + componentCount;
        //        _components[homogenousComponentIndex] = _one;
        //    }
        //}
        #endregion
    }
}
